listbase: Allocate rubberband according to list coords
authorBenjamin Otte <otte@redhat.com>
Wed, 24 Jun 2020 03:02:04 +0000 (05:02 +0200)
committerBenjamin Otte <otte@redhat.com>
Fri, 26 Jun 2020 05:13:32 +0000 (07:13 +0200)
The rubberband is now handled on the list coordinate system.

When starting the rubberband, we track the item under the pointer and
follow it when it is moving.
This may lead to the rubberband start position changing position and
while this may be confusing, it alerts users to the fact that something
crazy is going on.

gtk/gtklistbase.c

index 70036eedf3e79a3ee953ac2be700253f2e2e44d4..2568b0604ed47d590b49ed20743d8cbf45f17169 100644 (file)
@@ -41,24 +41,18 @@ typedef struct _RubberbandData RubberbandData;
 
 struct _RubberbandData
 {
-  GtkWidget *widget;
+  GtkWidget *widget;                            /* The rubberband widget */
+
+  GtkListItemTracker *start_tracker;            /* The item we started dragging on */
+  double              start_align_across;       /* alignment in horizontal direction */
+  double              start_align_along;        /* alignment in vertical direction */
+
   GtkBitset *active;
-  double x1, y1;
-  double x2, y2;
+  double pointer_x, pointer_y;                  /* mouse coordinates in widget space */
   gboolean modify;
   gboolean extend;
 };
 
-static void
-rubberband_data_free (gpointer data)
-{
-  RubberbandData *rdata = data;
-
-  g_clear_pointer (&rdata->widget, gtk_widget_unparent);
-  g_clear_pointer (&rdata->active, gtk_bitset_unref);
-  g_free (rdata);
-}
-
 typedef struct _GtkListBasePrivate GtkListBasePrivate;
 
 struct _GtkListBasePrivate
@@ -1387,30 +1381,82 @@ gtk_list_base_size_allocate_child (GtkListBase *self,
   gtk_widget_size_allocate (child, &child_allocation, -1);
 }
 
+static void
+gtk_list_base_widget_to_list (GtkListBase *self,
+                              double       x_widget,
+                              double       y_widget,
+                              int         *across_out,
+                              int         *along_out)
+{
+  GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+  GtkWidget *widget = GTK_WIDGET (self);
+
+  if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
+    x_widget = gtk_widget_get_width (widget) - x_widget;
+
+  gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), across_out, NULL, NULL);
+  gtk_list_base_get_adjustment_values (self, priv->orientation, along_out, NULL, NULL);
+
+  if (priv->orientation == GTK_ORIENTATION_VERTICAL)
+    {
+      *across_out += x_widget;
+      *along_out += y_widget;
+    }
+  else
+    {
+      *across_out += y_widget;
+      *along_out += x_widget;
+    }
+}
+
 void
 gtk_list_base_allocate_rubberband (GtkListBase *self)
 {
   GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-  GdkRectangle rect;
-  double x, y;
-  int min, nat;
+  GtkRequisition min_size;
+  int x1, x2, y1, y2;
+  int offset_x, offset_y;
 
   if (!priv->rubberband)
     return;
 
-  gtk_widget_measure (priv->rubberband->widget,
-                      GTK_ORIENTATION_HORIZONTAL, -1,
-                      &min, &nat, NULL, NULL);
+  if (priv->rubberband->start_tracker == NULL)
+    {
+      x1 = 0;
+      y1 = 0;
+    }
+  else
+    {
+      guint pos = gtk_list_item_tracker_get_position (priv->item_manager, priv->rubberband->start_tracker);
 
-  x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
-  y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
+      if (gtk_list_base_get_allocation_along (self, pos, &y1, &y2) &&
+          gtk_list_base_get_allocation_across (self, pos, &x1, &x2))
+        {
+          x1 += x2 * priv->rubberband->start_align_across;
+          y1 += y2 * priv->rubberband->start_align_along;
+        }
+      else
+        {
+          x1 = 0;
+          y1 = 0;
+        }
+    }
+
+  gtk_list_base_widget_to_list (self,
+                                priv->rubberband->pointer_x, priv->rubberband->pointer_y,
+                                &x2, &y2);
+
+  gtk_widget_get_preferred_size (priv->rubberband->widget, &min_size, NULL);
 
-  rect.x = MIN (priv->rubberband->x1, priv->rubberband->x2) - x;
-  rect.y = MIN (priv->rubberband->y1, priv->rubberband->y2) - y;
-  rect.width = ABS (priv->rubberband->x1 - priv->rubberband->x2) + 1;
-  rect.height = ABS (priv->rubberband->y1 - priv->rubberband->y2) + 1;
+  gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &offset_x, NULL, NULL);
+  gtk_list_base_get_adjustment_values (self, priv->orientation, &offset_y, NULL, NULL);
 
-  gtk_widget_size_allocate (priv->rubberband->widget, &rect, -1);
+  gtk_list_base_size_allocate_child (self,
+                                     priv->rubberband->widget,
+                                     MIN (x1, x2) - offset_x,
+                                     MIN (y1, y2) - offset_y,
+                                     MAX (min_size.width, ABS (x1 - x2) + 1),
+                                     MAX (min_size.height, ABS (y1 - y2) + 1));
 }
 
 static void
@@ -1421,18 +1467,29 @@ gtk_list_base_start_rubberband (GtkListBase *self,
                                 gboolean     extend)
 {
   GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
-  double value_x, value_y;
+  cairo_rectangle_int_t item_area;
+  int list_x, list_y;
+  guint pos;
 
   if (priv->rubberband)
     return;
 
-  value_x = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
-  value_y = gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
+  gtk_list_base_widget_to_list (self, x, y, &list_x, &list_y);
+  if (!gtk_list_base_get_position_from_allocation (self, list_x, list_y, &pos, &item_area))
+    {
+      g_warning ("Could not start rubberbanding: No item\n");
+      return;
+    }
 
   priv->rubberband = g_new0 (RubberbandData, 1);
 
-  priv->rubberband->x1 = priv->rubberband->x2 = x + value_x;
-  priv->rubberband->y1 = priv->rubberband->y2 = y + value_y;
+  priv->rubberband->start_tracker = gtk_list_item_tracker_new (priv->item_manager);
+  gtk_list_item_tracker_set_position (priv->item_manager, priv->rubberband->start_tracker, pos, 0, 0);
+  priv->rubberband->start_align_across = (double) (list_x - item_area.x) / item_area.width;
+  priv->rubberband->start_align_along = (double) (list_y - item_area.y) / item_area.height;
+
+  priv->rubberband->pointer_x = x;
+  priv->rubberband->pointer_y = y;
 
   priv->rubberband->modify = modify;
   priv->rubberband->extend = extend;
@@ -1487,7 +1544,12 @@ gtk_list_base_stop_rubberband (GtkListBase *self)
       gtk_bitset_unref (mask);
     }
 
-  g_clear_pointer (&priv->rubberband, rubberband_data_free);
+  gtk_list_item_tracker_free (priv->item_manager, priv->rubberband->start_tracker);
+  g_clear_pointer (&priv->rubberband->widget, gtk_widget_unparent);
+  g_clear_pointer (&priv->rubberband->active, gtk_bitset_unref);
+  g_free (priv->rubberband);
+  priv->rubberband = NULL;
+
   remove_autoscroll (self);
 
   gtk_widget_queue_draw (GTK_WIDGET (self));
@@ -1503,8 +1565,8 @@ gtk_list_base_update_rubberband (GtkListBase *self,
   if (!priv->rubberband)
     return;
 
-  priv->rubberband->x2 = x + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_HORIZONTAL]);
-  priv->rubberband->y2 = y + gtk_adjustment_get_value (priv->adjustment[GTK_ORIENTATION_VERTICAL]);
+  priv->rubberband->pointer_x = x;
+  priv->rubberband->pointer_y = y;
 
   gtk_list_base_update_rubberband_selection (self);